﻿# define _CRT_SECURE_NO_WARNINGS 1
//第22讲：动态内存管理
//int val = 20;//在栈空间上开辟四个字节 
//char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间 
//空间开辟⼤⼩是固定的。
//• 数组在申明的时候，必须指定数组的⻓度，数组空间⼀旦确定了⼤⼩不能调整
//但是对于空间的需求，不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知
//道，那数组的编译时开辟空间的⽅式就不能满⾜了。
//C语⾔引⼊了动态内存开辟，让程序员⾃⼰可以申请和释放空间，就⽐较灵活了。
//不同数据在存放在内存中的什么不同位置
//栈区：局部变量
//堆区：动态内存开辟（malloc,calloc,rcalloc,free)
//静态区：全局变量，静止变量
//2. malloc和free
//void* malloc(size_t size);
//功能：向内存的堆区申请⼀块连续可⽤的空间，并返回指向这块空间的起始地址。
//• 如果开辟成功，则返回这块空间的起始地址。
//• 如果开辟失败，则返回⼀个 NULL 指针，因此malloc的返回值⼀定要做检查。
//• 返回值的类型是 void* ，所以malloc函数并不知道开辟空间的类型，具体在使⽤的时候使⽤者⾃
//⼰来决定。
//• 如果参数 size 为0，malloc的⾏为是标准是未定义的，取决于编译器。
//#include <assert.h>
//int main()
//{
//	int* c = malloc(5);
//	assert(c);
//	return 0;
//}
////2.2 free
////C语⾔提供了另外⼀个函数free，专⻔是⽤来做动态内存的释放和回收的，函数原型如下：
//void free(void* ptr);
//free函数⽤来释放动态开辟的内存。
//• 如果参数 ptr 指向的空间不是动态开辟的，那free函数的⾏为是未定义的。
//• 如果参数 ptr 是NULL指针，则函数什么事都不做。
//malloc和free都声明在 stdlib.h 头⽂件中
//举个例⼦
#include <stdlib.h>
#include <stdio.h>
//int main()
//{
//	int num = 0;
//	scanf("%d", &num);
//	int* ptr = 0;
//	ptr = malloc(num * sizeof(int));
//	if (ptr == NULL)
//	{
//		printf("开辟内存失败\n");
//		return 1;
//	}
//	int i = 0;
//	for (i = 0; i < num; i++)
//	{
//		*(ptr + i) = i;
//		printf("%d ", ptr[i]);
//	}
//	printf("\n");
//	free(ptr);
//	ptr = NULL;
//	return 0;
//}
//#include <stdlib.h>
//#include <stdio.h>
//
//int main() {
//    int num = 0;
//    printf("请输入数组大小（正整数）：");
//
//    // 验证输入是否为有效整数
//    if (scanf("%d", &num) != 1) {
//        printf("错误：输入不是有效的整数！\n");
//        return 1;
//    }
//
//    // 清除输入缓冲区中的多余字符
//    while (getchar() != '\n');
//
//    // 检查数值合法性
//    if (num <= 0) {
//        printf("错误：数组大小必须是正整数！\n");
//        return 1;
//    }
//
//    // 动态分配内存
//    int* ptr = malloc(num * sizeof(int));
//    if (ptr == NULL) {
//        printf("开辟内存失败\n");
//        return 1;
//    }
//
//    // 初始化并打印数组元素
//    for (int i = 0; i < num; i++) {
//        ptr[i] = i;  // 等价于 *(ptr + i) = i;
//        printf("%d ", ptr[i]);
//    }
//    printf("\n");
//
//    // 释放内存
//    free(ptr);
//    ptr = NULL;
//
//    return 0;
//}
//3. calloc和realloc
//3.1 calloc
//C语⾔还提供了⼀个函数叫 calloc ， calloc 函数也⽤来动态内存分配。原型如下：
//void* calloc(size_t num, size_t size);
//• 函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间，并且把空间的每个字节初始化为0。
//• 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全
//0。
//int main()
//{
//	int num = 0;
//	printf("请输入正整数：");
//	scanf("%d",&num);
//	int* p = calloc(num, sizeof(int));
//	if (p != NULL)
//	{
//		int i = 0;
//		for (i = 0; i < num; i++)
//		{
//			/**(p + i) = i;*/
//			printf("%d ", p[i]);
//		}
//	}
//	free(p);
//	p = NULL;
//	return 0;
//}
//输出结果：
////请输入正整数：10
////0 0 0 0 0 0 0 0 0 0
//• 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全
////0。
//所以如果我们对申请的内存空间的内容要求初始化，那么可以很⽅便的使⽤calloc函数来完成任务
//3.2 realloc
//• realloc函数的出现让动态内存管理更加灵活。
//• 有时会我们发现过去申请的空间太⼩了，有时候我们⼜会觉得申请的空间过⼤了，那为了合理的使
//⽤内存，我们⼀定会对内存的⼤⼩做灵活的调整。那 realloc 函数就可以做到对动态开辟内存⼤
//⼩的调整。
//函数原型如下：
//1 void* realloc(void* ptr, size_t size);
//ptr 是要调整的内存地址
//• size 调整之后新⼤⼩，单位是字节
//• 返回值为调整之后的内存起始位置。
//• 这个函数调整原内存空间⼤⼩的基础上，还会将原来内存中的数据移动到 新 的空间。
//• realloc在调整内存空间的是存在两种情况：
//◦ 情况1：原有空间之后有⾜够⼤的空间
//◦ 情况2：原有空间之后没有⾜够⼤的空间
//情况1
//当是情况1的时候，要扩展内存就直接原有内存之后直接追加空间，原来空间的数据不发⽣变化。
//情况2
//当是情况2的时候，原有空间之后没有⾜够多的空间时，扩展的⽅法是：在堆空间上另找⼀个合适⼤⼩
//的连续空间来使⽤。这样函数返回的是⼀个新的内存地址。
//做法;讲旧空间的数据拷贝一份到新的空间
//返回新的空间的起始地址
//释放掉旧的地址
//int main()
//{
//	//向内存申请空间，来存储1~5
//	int*  p = (int*)malloc(5 * sizeof(int));
//	if (p == NULL)
//	{
//		perror(malloc);
//		return 1;
//	}
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		p[i] = i+ 1;
//	}
//	//后来想要继续存放6~10，空间不够，用reaclloc来调整申请的空间大小
//	int *p2 = realloc(p, 10 * sizeof(int));
//	if (p2 == NULL)
//	{
//		perror(realloc);
//		free(p);
//		p = NULL;
//		return 1;
//	}
//	for (i = 5; i < 10; i++)
//	{
//		p[i] = i + 1;
//	}
//	p = p2;
//	free(p);
//	return 0;
//}
//realloc 也可以用来开辟空间
//int main()
//{
//	int * p = (int *)preaclloc(NULL, 40);//等价于malloc(40)
//	return 0;
//}
//4. 常⻅的动态内存的错误
//4.1 对NULL指针的解引⽤操作//没有对malloc.calloc,recalloc的返回值做判断
//void test()
//{
//	int* p = (int*)malloc(INT_MAX / 4);
//	*p = 20;//如果p的值是NULL，就会有问题 
//	free(p);
//}
//4.2 对动态开辟空间的越界访问
//void test()
//{
//	int i = 0;
//	int* p = (int*)malloc(10 * sizeof(int));
//	if (NULL == p)
//	{
//		exit(EXIT_FAILURE);
//	}
//	for (i = 0; i <= 10; i++)
//	{
//		*(p + i) = i;//当i是10的时候越界访问 
//	}
//	free(p);
//}
//对⾮动态开辟内存使⽤free释放 free只能释放malloc.calloc,recalloc开辟的空间
// void test() {
//	int a = 10;
//	int* p = &a;
//	free(p);//ok?
//}
//使⽤free释放⼀块动态开辟内存的⼀部分//free释放空间的，一定要给空间的起始地址
//void test()
//{
//	int* p = (int*)malloc(100);
//	p++;
//	free(p);//p不再指向动态内存的起始位置 
//}
//4.5 对同⼀块动态内存多次释放
//void test()
//{
//	int* p = (int*)malloc(100);
//	free(p);
//	free(p);//重复释放 
//}
//动态开辟内存忘记释放（内存泄漏）
//void test()
//{
//	int* p = (int*)malloc(100);
//	if (NULL != p)
//	{
//		*p = 20;
//	}
//}
//谁申请的空间谁释放
//自己申请的空间及时释放
//自己释放（函数1）不了的，告诉别人（函数1）释放
//malloc 和free成对出现
//#include <stdio.h>
//#include <stdlib.h>
//#include <string.h>
//void GetMemory(char* p)
//{
//	p = (char*)malloc(100);
//}
//void Test(void)
//{
//	char* str = NULL;
//	GetMemory(str);
//	strcpy(str, "hello world");
//	printf(str);
//}
//int main()
//{
//	Test();
//	return 0;
//}
//程序崩溃原因
//一、核心问题：传值调用导致内存泄漏
//GetMemory 函数通过值传递接收指针参数 p，这意味着：
//形参 p 是实参 str 的副本：修改 p 的值（如分配内存）不会影响原始指针 str。
//内存分配发生在副本上：p = malloc(100) 仅改变了 p 指向的地址，而 str 仍为 NULL。
//内存泄漏：malloc 分配的内存地址无法被外部访问，且未被释放。
//二、崩溃触发点：对空指针执行 strcpy
//c
//运行
//strcpy(str, "hello world");  // str 仍为 NULL
//strcpy 尝试将字符串复制到 NULL 指针，导致段错误（Segmentation Fault）。
//三、其他潜在问题
//未检查 malloc 返回值：若内存分配失败，p 会是 NULL，但函数未处理这种情况。
//直接使用用户输入作为 printf 参数：printf(str) 存在格式字符串漏洞，建议使用 printf("%s", str)。
//内存未释放：即使 GetMemory 正确工作，str 指向的内存也未被 free。
//#include <stdio.h>
//#include <stdlib.h>
//char* GetMemory(void)
//{
//	char p[] = "hello world";
//	return p;
//}
//void Test(void)
//{
//	char* str = NULL;
//	str = GetMemory();
//	printf(str);
//}
//int main()
//{
//	Test();
//	return 0;
//}
//一、核心问题：局部数组的生命周期结束后仍被引用
//c
//运行
//char* GetMemory(void) {
//	char p[] = "hello world";  // 局部数组，存储在栈上
//	return p;  // 返回局部数组的地址
//}
//局部数组 p 的生命周期：
//p 是 GetMemory 函数内的局部变量，存储在栈帧中。当函数返回时，其栈帧被销毁，p 的内存不再有效。
//返回局部地址的后果：
//return p 返回的是已销毁内存的地址，导致 str 成为悬空指针。后续对 str 的访问（如 printf(str)）会读取无效内存，引发未定义行为（可能崩溃或输出乱码）。
//二、其他潜在问题
//字符串字面量与字符数组的区别：
//char p[] = "hello world"; 创建的是可修改的数组，而非字符串字面量。但即使改为 const char* p = "hello world"; ，返回的仍是局部指针，问题依旧存在。
//格式字符串漏洞：
//printf(str) 直接使用用户输入作为格式字符串，存在安全风险（如 str 包含% n）。建议使用 printf("%s", str)。
//#include <stdio.h>
//#include <stdlib.h>
//
//#include <string.h>
//void GetMemory(char** p, int num)
//{
//	*p = (char*)malloc(num);//没有释放malloc开辟的空间，造成内存泄露 
//}
//void Test(void)
//{
//	char* str = NULL;
//	GetMemory(&str, 100);
//	strcpy(str, "hello");
//	printf(str);
//}
//
//int main()
//{
//	Test();
//	return 0;
//}
//#include <stdio.h>
//#include <stdlib.h>
//#include <string.h>
//void Test(void)
//{
//	char* str = (char*)malloc(100);
//	strcpy(str, "hello");
//	free(str);
//	if (str != NULL)
//	{
//		//str在这里是野指针，非法访问内存
//		strcpy(str, "world");
//		printf(str);
//	}
//}
//int main()
//{
//	Test();
//	return 0;
//}
//一、核心问题：释放内存后未将指针置为 NULL
//c
//运行
//free(str);  // 释放内存，但 str 仍指向原地址（成为野指针）
//if (str != NULL) {  // 条件为真，因为 free 不会改变指针值
//	strcpy(str, "world");  // 向已释放的内存写入数据！
//	printf(str);  // 读取已释放的内存！
//}
//
//free(str) 的作用：
//释放 str 指向的内存，将其标记为可被系统重新分配。
//不会改变 str 的值，str 仍指向原内存地址，成为野指针（Dangling Pointer）。
//悬空指针的危害：
//后续对 str 的访问（如 strcpy、printf）会操作已释放的内存，可能导致：
//数据 corruption（破坏其他变量）
//程序崩溃（段错误）
//安全漏洞（攻击者可能控制已释放的内存）
//一、野指针的定义
//野指针（Dangling Pointer） 是指指向 “不确定内存地址” 的指针，其指向的内存可能：
//已被释放（最常见情况）
//从未被分配
//超出作用域（如局部变量栈空间已释放）
//与空指针（NULL Pointer） 不同：
//空指针明确指向地址0，而野指针指向的地址是随机的、无意义的。
//二、野指针的三大成因
//1. 内存释放后未置为 NULL
//2. 返回局部变量的地址
//3. 指针未初始化
//6. 柔性数组
//也许你从来没有听说过柔性数组（flexiblearray）这个概念，但是它确实是存在的。
//C99中，结构中的最后⼀个元素允许是未知⼤⼩的数组，这就叫做『柔性数组』成员。
//例如：
//struct st_type
//{
//	int i;
//	int a1[0];//柔性数组成员 
//	/*有些编译器会报错⽆法编译可以改成：*/
//	//int a[];//柔性数组成员 
//
//};
//6.1 柔性数组的特点：
//• 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
//• sizeof返回的这种结构⼤⼩不包括柔性数组的内存。
//• 包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配，并且分配的内存应该⼤于结构的⼤
//⼩，以适应柔性数组的预期⼤⼩。
//int main()
//{
//	printf("%zu\n", sizeof(struct st_type));
//	//s输出结果：4不包括int a1[0]的大小
//	return 0;
//}
//6.2 柔性数组的使⽤
//代码1
//struct s
//{
//	int n;
//	int arr[];//柔性数组
//};
//int main()
//{                                   //为这个结构体开辟的空间    为柔性数组开辟的空间
//	struct s* p = (struct s*) malloc(sizeof(struct s) + 10 * sizeof(int));
//	p->n = 100;
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	//开辟成功，开始使用
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		p->arr[i] = i + 1;
//		printf("%d ", p->arr[i]);
//	}
//	printf("\n");			
//	//空间不够，调整空间
//	struct s* tmp = (struct s*)realloc(p, sizeof(struct s) + 20 * sizeof(int));
//	if (tmp == NULL)
//	{
//		perror("realloc");
//		return 1;
//	}
//	p = tmp;
//	for(i = 10; i < 20; i++)
//	{
//		p->arr[i] = i + 1;
//		printf("%d ", p->arr[i]);
//	}
//	//释放空间
//	free(p);
//	p = NULL;
//	return 0;
//}
//代码2
//struct s
//{
//	int n;
//	int* arr;
//};
//int main()
//{
//	struct s* p = (struct s*)malloc(sizeof(struct s));
//	p->n = 100;
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	//开辟成功
//	//为arr开辟空间
//	int*ptr =(int *)malloc(10 * sizeof(int));
//	if (ptr == NULL)
//	{
//		perror("malloc2");
//		return 1;
//	}
//	p->arr = ptr;
//	//使用
//
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		p->arr[i] = i;
//		printf("%d ", p->arr[i]);
//	}
//	printf("\n");
//	//空间不够，调整空间
//	ptr =(int *)realloc(p->arr, 20 * sizeof(int));
//	if (ptr == NULL)
//	{
//		perror("realloc");
//		return 1;
//	}
//	p->arr = ptr;
//	for (i = 10; i < 20;i++)
//	{
//		p->arr[i] = i + 1;
//		printf("%d ",p->arr[i]);
//	}
//	//释放空间
//	free(p->arr);//要先释放
//	free(p);
//	return 0;
//}
上述 代码1 和 代码2 可以完成同样的功能，但是 ⽅法1 的实现有两个好处：第⼀个好处是：⽅便内存释放
如果我们的代码是在⼀个给别⼈⽤的函数中，你在⾥⾯做了⼆次内存分配，并把整个结构体返回给⽤
⼾。⽤⼾调⽤free可以释放结构体，但是⽤⼾并不知道这个结构体内的成员也需要free，所以你不能
指望⽤⼾来发现这个事。所以，如果我们把结构体的内存以及其成员要的内存⼀次性分配好了，并返
回给⽤⼾⼀个结构体指针，⽤⼾做⼀次free就可以把所有的内存也给释放掉。
第⼆个好处是：这样有利于访问速度.
连续的内存有益于提⾼访问速度，也有益于减少内存碎⽚。（其实，我个⼈觉得也没多⾼了，反正你
跑不了要⽤做偏移量的加法来寻址）
7. 总结C / C++中程序内存区域划分
C / C++程序内存分配的⼏个区域：
1. 栈区（stack）：在执⾏函数时，函数内局部变量的存储单元都可以在栈上创建，函数执⾏结束时
这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中，效率很⾼，但是分配的内
存容量有限。栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等。
《函数栈帧的创建和销毁》
2. 堆区（heap）：⼀般由程序员分配释放，若程序员不释放，程序结束时可能由OS（操作系统）
回收。分配⽅式类似于链表。
3. 数据段（静态区）：（static）存放全局变量、静态数据。程序结束后由系统释放。
4. 代码段：存放函数体（类成员函数和全局函数）的⼆进制代码。
